﻿using System;
using System.Collections.Generic;
using System.Linq;
using NMF.Utilities;
using System.Text;

namespace NMF.Languages.Adaption
{
    public class AdaptiveTypeSystem : ITypeSystem
    {
        private int generateCount = 0;

        public AdaptiveTypeSystem()
        {
            typeDict = new Dictionary<string, IType>();
            InitializeStandardTypes();
        }

        protected virtual void InitializeStandardTypes()
        {
        }

        private Dictionary<string, IType> typeDict;

        public IType GetType(string typeName)
        {
            IType output;
            if (typeDict.TryGetValue(typeName, out output))
            {
                return output;
            }
            else
            {
                output = new AdaptiveClass(typeName, this);
                typeDict.Add(typeName, output);
                return output;
            }
        }

        public IClass GetCommonBase(IClass class1, IClass class2)
        {
            if (class1 == class2) return class1;

            AdaptiveClass class1A = class1 as AdaptiveClass;
            AdaptiveClass class2A = class2 as AdaptiveClass;

            if (class1A == null) return class2A;
            if (class2A == null) return class1A;

            var basesClass1 = new HashSet<AdaptiveClass>(class1A.Closure(c => c.GetBaseTypes().AllOfType<AdaptiveClass>()));
            var basesClass2 = new HashSet<AdaptiveClass>(class2A.Closure(c => c.GetBaseTypes().AllOfType<AdaptiveClass>()));

            if (basesClass1.Contains(class2A)) return class2A;
            if (basesClass2.Contains(class1A)) return class1A;

            basesClass1.IntersectWith(basesClass2);

            if (basesClass1.Count > 0)
            {
                return basesClass1.First();
            }
            else
            {
                var newBase = new AdaptiveClass("GeneratedClass" + (++generateCount).ToString(), this);
                class1A.AssertIsAssignableFrom(newBase);
                class2A.AssertIsAssignableFrom(newBase);
                typeDict.Add(newBase.Name, newBase);
                return newBase;
            }
        }

        public IEnumerable<IType> Types
        {
            get { return typeDict.Values; }
        }

        public void AssertDataType(DataType dataType)
        {
            if (!typeDict.ContainsKey(dataType.Name))
            {
                typeDict.Add(dataType.Name, dataType);
            }
            else
            {
                if (typeDict[dataType.Name] != dataType) throw new AssertionException();
            }
        }
    }
}
